using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;

using Atomic.Libraries;
using Atomic.Structures;

namespace Atomic.Clusters.Fitting
{
	[Serializable]
	public class AutomaticClusterExpansion : ClusterExpansion
	{
		//[NonSerialized] private Dictionary<Atom, double> referenceEnergies;

		internal AutomaticClusterExpansion(DirectoryInfo directory, IEnumerable<StructureMapping> fittedEnergies) // copy of structures, does not roundtrip
			: base(null, LoadExpansionTerms(directory))
		{
			// Start by reading reference energies, as that is used for the parsing.
			string[] atoms = File.ReadAllLines(directory.AddFile("atoms.out").FullName);
			string[] refs = File.ReadAllLines(directory.AddFile("ref_energy.out").FullName);
			ReferenceEnergies = new ReadOnlyList<AtomMapping>(atoms.Zip(refs, (a, r) => new AtomMapping(Atom.Parse(a), double.Parse(r, CultureInfo.InvariantCulture))));

			// Then read fitted energies.
			List<StructureMapping> energies = new List<StructureMapping>();
			List<StructureMapping> fittedEnergies2 = new List<StructureMapping>();
			foreach (string line in File.ReadLines(directory.AddFile("fit.out").FullName).OrderBy(s => int.Parse(s.Split(' ')[s.Split(' ').Length - 1].Substring(1))))
			{
				string[] cols = line.Split(' ');
				string name = cols[cols.Length - 1];
				//double weight = double.Parse(cols[cols.Length - 2], CultureInfo.InvariantCulture);
				double relativeFittedEnergyPerSite = double.Parse(cols[cols.Length - 4], CultureInfo.InvariantCulture);
				double relativeEnergyPerSite = double.Parse(cols[cols.Length - 5], CultureInfo.InvariantCulture);

				// Read original full precision energy from original file.
				DirectoryInfo d = directory.AddDirectory(name);
				Structure structure = Structure.Read(d.AddFile("str.out"));
				double energy = double.Parse(File.ReadAllText(d.AddFile("energy").FullName), CultureInfo.InvariantCulture);

				double referenceEnergyPerSite = UnscaledReferenceEnergy(structure);
				double energyPerSite = relativeEnergyPerSite + referenceEnergyPerSite;

				// Now the scaling is implicitly determined as we know the absolute energy.
				double sites = energy / energyPerSite;
				//double sites = 6.0; //FIXME

				double fittedEnergy = (relativeFittedEnergyPerSite + referenceEnergyPerSite) * sites;

				energies.Add(new StructureMapping(structure, energy));
				fittedEnergies2.Add(new StructureMapping(structure, fittedEnergy));

				Console.WriteLine(sites); // FIXME
			}

			/*//if (fittedEnergies == null)
			{
				// Read from file system instead.
				fittedEnergies = new List<StructureMapping>();
				foreach (DirectoryInfo d in directory.GetDirectories())
				{
					int k;
					if (int.TryParse(d.Name, out k) || d.Name.StartsWith("_") && int.TryParse(d.Name.Substring(1), out k))
					{
						Structure s = Structure.Read(d.AddFile("str.out"));
						double e = double.Parse(File.ReadAllText(d.AddFile("energy").FullName), CultureInfo.InvariantCulture);
						((List<StructureMapping>)fittedEnergies).Add(new StructureMapping(s, e));
					}
				}
			}*/
			Energies = new ReadOnlyList<StructureMapping>(energies);
			FittedEnergies = new ReadOnlyList<StructureMapping>(fittedEnergies2);

			foreach (string s in File.ReadLines(directory.AddFile("maps.log").FullName))
			{
				if (s.StartsWith("Crossvalidation score:"))
				{
					CrossValidationScore = double.Parse(s.Split(':')[1].Trim(), CultureInfo.InvariantCulture);
				}
			}

			if (File.Exists(directory.AddFile("maps.log").FullName))
			{
				LogOutput = File.ReadAllText(directory.AddFile("maps.log").FullName);
			}

		}

		private static Structure LoadLattice(DirectoryInfo directory)
		{
			return Structure.Read(directory.AddFile("lat.in"));
		}

		private static IEnumerable<ClusterExpansionTerm> LoadExpansionTerms(DirectoryInfo directory)
		{
			Structure lattice = LoadLattice(directory);
			string[] eci = File.ReadAllLines(directory.AddFile("eci.out").FullName);

			int n = 0;
			foreach (ClusterClass cluster in new ClusterFinder(lattice).Read(directory.AddFile("clusters.out")))
			{
				yield return new ClusterExpansionTerm(cluster, double.Parse(eci[n++], CultureInfo.InvariantCulture));
			}
		}

		public static AutomaticClusterExpansion Read(DirectoryInfo directory)
		{
			return new AutomaticClusterExpansion(directory, null);
		}

		/*public static AutomaticClusterExpansion Fit(Structure lattice, IEnumerable<Structure> structures, IEnumerable<double> energies)
		{
			return Fit(lattice, new List<Structure>(structures).ToArray(), new List<double>(energies).ToArray());
		}

		public static AutomaticClusterExpansion Fit(Structure lattice, IDictionary<Structure, double> energies)
		{
			// Proper order guaranteed:
			// http://msdn.microsoft.com/en-us/library/ekcfxy3x.aspx
			return Fit(lattice, energies.Keys, energies.Values);
		}*/

		public static AutomaticClusterExpansion Fit(Structure lattice, IEnumerable<StructureMapping> energies)
		{
			AutomaticClusterExpansionFitter fitter = new AutomaticClusterExpansionFitter(lattice, energies);
			fitter.PrintConsole = false;
			return fitter.Fit();
		}

		public override double ExpandEnergy(Structure structure, IClusterFunctionEvaluator evaluator)
		{
			// Energy contribution from cluster expansion computed in base class.
			return ReferenceEnergy(structure) + base.ExpandEnergy(structure, evaluator);
		}

		private double UnscaledReferenceEnergy(Structure structure)
		{
			double e = 0.0;
			foreach (AtomMapping referenceEnergy in ReferenceEnergies)
			{
				// Reference energies are added according to concentration.
				foreach (Site site in structure.Sites)
				{
					if (site.Atom == referenceEnergy.Atom)
					{
						e += referenceEnergy.Value;
					}
				}
			}

			// Normalize concentration.
			e /= structure.Sites.Count;

			return e;
		}

		public double ReferenceEnergy(Structure structure)
		{
			/*if (referenceEnergies == null)
			{
				referenceEnergies = new Dictionary<Atom, double>();
				foreach (AtomMapping a in ReferenceEnergies)
				{
					referenceEnergies.Add(a.Atom, a.Value);
				}
			}*/

			// Number of active sites.
			int sites = Lattice.Sites.Count(site => site.Atom is PlaceholderAtom) * structure.Sites.Count / Lattice.Sites.Count;

			return UnscaledReferenceEnergy(structure) * sites;
		}

		/*public int LatticeActiveSites
		{
			get;
			private set;
		}*/

		public ReadOnlyList<StructureMapping> Energies
		{
			get;
			private set;
		}

		public ReadOnlyList<StructureMapping> FittedEnergies
		{
			get;
			private set;
		}

		public ReadOnlyList<AtomMapping> ReferenceEnergies
		{
			get;
			private set;
		}

		public double CrossValidationScore
		{
			get;
			private set;
		}

		public string LogOutput
		{
			get;
			private set;
		}

		public string FittingOutput
		{
			get;
			private set;
		}
	}
}
